/* object_refs.js --- Remote references to Javascript objects
  Copyright (C) 2006 by Lukas Loehrer
  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA

  Author: Lukas Loehrer <loehrerl [at] gmx [dot] net>
*/

var logger = module.require("package", "lib/filelogger").logger;

// This is a service which can emit events
var EventService = module.require("class", "event_service");
var inheritor = new EventService();

var testclass = module.require("class", "tests/test_remote_class");


function constructor(name, remote) {
	EventService.call(this, name, remote);

	// javascript objects are identified by unique id across network
	this._nextObjectId = 1;
	// Maps numberic boject IDs to javascript objects
	this._liveRefs = {};

	this._creatableTypes = {};
	this._creatableTypes["TestClass"] = testclass;

	// Add before advice to serializer
	var _this = this; // make this lexically scoped
	remote.getSerializer().addObjectBeforeAdvice(_decorateObjectRefsAdvice);

	// Set up events specific for this class
	this._events["outputAvailable"] =true;
}

function _disconnectService() {
	EventService.prototype._disconnectService.call(this);

	this._liveRefs = null;
	this._creatableTypes = null;
}

function _decorateObjectRefsAdvice(object) {
	if (object != null && typeof(object) == "object" && isBlessed(object)) {
		return object[passByReferenceMarker]._getObjectDescriptor(object);
	} else {
		// Or this object needs no decoration
		return object;
	}
}

// {{{Public methods 

// Client informs us about the references that have become garbage in its context.
function collectGarbage(clientRefs, receiveCount) {
	if (this._remote._sendCount > receiveCount) {
		// Not a consistent snapshot
		return null;
	}
	var liveRefs = this._liveRefs, obj;
	for each (var deadID in clientRefs) {
		obj = liveRefs[deadID];
		if (obj) {
			delete obj[objectIdName];
		}
		delete liveRefs[deadID];
	}
	return true;
}

function callMethod(objectDescriptor, methodName, args){
	var obj, result;
	
	obj = this._undecorateObjectRefs(objectDescriptor);
	args = this._undecorateObjectRefs(args);

	result = obj[methodName].apply(obj, args);

	return result;
}

function getProperty(objectDescriptor, propName) {
	var obj;
	obj = this._undecorateObjectRefs(objectDescriptor);
	return obj[propName];
}

function setProperty(objectDescriptor, propName, value) {
	var obj;
	obj = this._undecorateObjectRefs(objectDescriptor);
	value = this._undecorateObjectRefs(value);
	obj[propName] = value;
	return obj;
}

function createInstance(typeName) {
	var obj;
	var restArgs = [];
	for (var i = 1; i < arguments.legnth; ++i) {
		restArgs.push(arguments[i])
			}

	obj = new this._creatableTypes[typeName]();
	this.bless(obj);
	return obj;
}

// }}}

function _getObjectById(id) {
	if (id in this._liveRefs) {
		var obj = this._liveRefs[id];
		return obj;
	}else {
		throw ("No such Object id = " + id + "\n" + this._liveRefs.toSource());
	}
}

// {{{Object reference serialization 

// Property whose presence marks an object as pass by reference for serialization
const passByReferenceMarker = "~_FiremacsPassByReference_~";
// Remote object id for pass by reference objects
const objectIdName = "_firemacsRemoteObjectId";

// Marker for object descriptors. Must be the same in the client.
const magicMark = "~_MozremoteObjectType_~";

function bless(obj) {
	obj[passByReferenceMarker] = this;
}

function isBlessed(obj) {
	return (passByReferenceMarker in obj);
}

// convert references to javascript objects to object descriptors
function _decorateObjectRefs(value) {
	if (!(value instanceof Object)) {
		// Do nothing to strings and numbers
		return value;
	} else if (!(value instanceof Array) && this.isBlessed(value)) {
		// Replace pass by reference objects with their discriptors
		// This object is to be passed by reference
		return this._getObjectDescriptor(value);
	}

	// value is either a by value object or an array
	var result = new value.constructor();
	for (var i in value) {
		if (value.hasOwnProperty(i)) {
			result[i] = this._decorateObjectRefs(value[i]);
		}
	}
	return result;
}

// Returns object descriptor for javascript object
// Descriptor is representation of object that will be sent over the network.
// If object has never been passed by reference before, do the necessary bookkeeping
function _getObjectDescriptor(obj) {
	var id;
	if (!(obj.hasOwnProperty(objectIdName))) {
		// obj has never been passed by reference before
		id = this._nextObjectId++;
		obj[objectIdName] = id;
		this._liveRefs[id] = obj;
	} else {
		id = obj[objectIdName];
	}
	var result = { id: id };
	result[magicMark] = "object-ref";
	return result;
}

function _isObjectDescriptor(desc) {
	return (desc.hasOwnProperty(magicMark) && desc[magicMark] == "object-ref");
}

// Converts object descriptors back to javascript object references
function _undecorateObjectRefs(value) {
	if (!(value instanceof Object)) {
		// Do nothing for strings and numbers
		return value;
	} else if (!(value instanceof Array)) {
		if (this._isObjectDescriptor(value)) {
			// Its an object descriptor
			return this._getObjectById(value["id"]);
		}
	}
	// value is either a by value object or an array
	// Apply recursively
	for (var i in value) {
		if (value.hasOwnProperty(i)) {
			value[i] = this._undecorateObjectRefs(value[i]);
		}
	}
	return value;
}

// }}}


